Fold env-driven conditionals into command identity#53
Merged
Conversation
Symmetry gap in the builder: a config var used in a guard ($(MODE) in an ifeq) had its value folded into every guarded command's identity via the condition_config_vars -> Sticky-edge path; an env var used the same way did not. So flipping env TUP_PLATFORM between builds left the phi-node's branch commands byte-identical, the output file unchanged on disk, and change detection reported "Nothing to do" with a stale output. Close it by mirroring the existing config machinery, end to end. Add condition_env_vars to BuilderContext (symmetric to condition_config_vars). In process_conditional, capture used_env_vars from the condition's expansion and merge it into condition_env_vars under the same ScopeGuard that handles nested conditionals. In create_command_node, emit Sticky edges from those env Variable nodes to every command in the block. Also close the upstream half so the sticky edges land on real nodes: extract process_import's node-creation block into ensure_env_var_node(ctx, state, name, value) and call it from the on_env_var_used callback so TUP_PLATFORM/TUP_ARCH (auto-resolved from the environment, not via an explicit import) get the same Variable node any imported var would. process_import is refactored to delegate node creation to the same helper with no behavioral change to its env->cached->default fallback dance. compute_command_identity already folds every Sticky-linked Variable node, so no change is needed in dag.cpp and no on-disk format bump is required - the new sticky edges are persisted by serialize_edges automatically. Reproducer: [platform-incremental] / fixture platform_conditional_incremental. Both ifeq branches produce the same output file with distinct content; the platform string never appears in either command's text, so only the sticky edge can detect the flip. Red before, green after. Full suite green (466 cases, 23671 assertions). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the residual hidden-input gap noted in PR #52: when
TUP_PLATFORM(or any env var) is sourced from the environment and used inside a conditional likeifeq ($(TUP_PLATFORM),win32), flipping it between builds did not trigger a rebuild — the output stayed stale and the build reported "Nothing to do."The root cause: an asymmetry the builder built up over time
The builder already folded config vars used in a guard into every guarded command's identity, via
condition_config_vars→Stickyedge. The env-var path had no equivalent:@(VAR))used_config_vars→ stickycondition_config_vars→ sticky to every command in the block$(VAR)afterimport)used_env_vars→ stickyTUP_PLATFORM/TUP_ARCHfrom env (auto-resolved, noimport)used_env_varsbut no node exists for the sticky edge to point atWhen env
TUP_PLATFORMflipped between builds, the phi-node's two branch commands sat in the graph unchanged (same text, same outputs, same identity), the output file on disk was unchanged, and change detection saw nothing to do.What changed
Symmetric mirror of the config machinery, end to end:
condition_env_varsadded toBuilderContext(include/pup/graph/builder.hpp), symmetric tocondition_config_vars.process_conditionalcapturesused_env_varsfrom the condition's expansion and merges it intocondition_env_varsunder the sameScopeGuardthat already handles nested conditionals.create_command_nodeemitsStickyedges from those env Variable nodes to every command in the block — exact mirror of the existing config loop.And to make sure those sticky edges land on real nodes:
ensure_env_var_node(ctx, state, name, value)extracted fromprocess_import(itsVariable-node creation/update block).process_importis refactored to delegate to it, with no behavioral change to itsenv → cached → defaultfallback.on_env_var_usedcallback now calls the helper too, soTUP_PLATFORM/TUP_ARCH— auto-resolved fromgetenvwithout an explicitimport— get the same tracking node any imported var would.Effect on command identity
compute_command_identity(from PR #52) already folds everySticky-linked Variable node, so no change is needed indag.cpp. And no on-disk format change: the new sticky edges are persisted byserialize_edgesautomatically.Test plan
[platform-incremental]+ fixtureplatform_conditional_incremental: bothifeqbranches produce the same output file with distinct content; the platform string never appears in either command's text, so only the sticky edge can detect the flip. Build withTUP_PLATFORM=win32(assertsWINBUILD, no-op stability), then re-bind tolinux(asserts!is_noop()andNIXBUILD). Red before this change, green after.[config](the symmetric config path stays intact),[import](env_var_persist,env_dep_subprocess, equals-in-value),[platform](first-build platform_conditional),[idshift]and[envdep](the regression guards from Identify commands by a structural hash, not a string or position #52).clang-format,clang-include-cleaner, and scopedclang-tidyon changed files are clean for new code (the file's pre-existingstd::move-on-trivially-copyable /qualified-autostyle is unchanged).Out of scope
TUP_PLATFORM/TUP_ARCHresolved from the compile-time default (Builtinbank) need no tracking — constant per putup binary.🤖 Generated with Claude Code